/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2021.
 * Description: watchdog notifiers
 * Author: xiaowei
 * Create: 2019-07-18
 */

#include<linux/slab.h>
#include <linux/hal/watchdog.h>

static DEFINE_SPINLOCK(lock);
static struct wdt_notify softdog_notify_head = {
	.entry = LIST_HEAD_INIT(softdog_notify_head.entry),
};
static struct wdt_notify hwdog_notify_head = {
	.entry = LIST_HEAD_INIT(hwdog_notify_head.entry),
};

static int register_notify_common(wtd_notify_t func,
				struct wdt_notify *head)
{
	struct wdt_notify *node = NULL;
	unsigned long flags;

	if (!func) {
		pr_err("register notifier is null!\n");
		return -EINVAL;
	}

	node = kzalloc(sizeof(struct wdt_notify), GFP_KERNEL);
	if (!node) {
		pr_err("Kmalloc for wdt notify node failed!\n");
		return -ENOMEM;
	}
	spin_lock_irqsave(&lock, flags);
	list_add_tail(&node->entry, &head->entry);
	node->func = func;
	spin_unlock_irqrestore(&lock, flags);

	return 0;
}

static int unregister_notify_common(wtd_notify_t func,
				struct wdt_notify *head)
{
	struct wdt_notify *node = NULL;
	struct list_head *t = NULL;
	unsigned long flags;

	if (!func) {
		pr_err("unregister notifier is null!\n");
		return -EINVAL;
	}

	spin_lock_irqsave(&lock, flags);
	list_for_each(t, &head->entry) {
		node = list_entry(t, struct wdt_notify, entry);
		if (node == head)
			continue;

		if (node->func == func) {
			list_del(&node->entry);
			spin_unlock_irqrestore(&lock, flags);
			kfree(node);
			return 0;
		}
	}
	spin_unlock_irqrestore(&lock, flags);

	pr_err("Cannot find the unregister notifier %pS\n", func);
	return -ENODEV;
}

static int register_final_notify_common(wtd_notify_t func,
				struct wdt_notify *head)
{
	if (!func)
		return -EINVAL;

	if (head->func)
		return -EEXIST;

	head->func = func;
	return 0;
}

static int unregister_final_notify_common(struct wdt_notify *head)
{
	unsigned long flags;

	if (!head->func)
		return -ENODEV;

	spin_lock_irqsave(&lock, flags);
	head->func = NULL;
	spin_unlock_irqrestore(&lock, flags);

	return 0;
}

static inline void call_handle(const struct wdt_notify *node)
{
	if (node->func)
		node->func();
}

static void call_notify_common(const struct wdt_notify *head)
{
	const struct wdt_notify *node = NULL;
	struct list_head *t = NULL;
	unsigned long flags;

	spin_lock_irqsave(&lock, flags);
	list_for_each(t, &head->entry) {
		call_hwdog_handle();
		node = list_entry(t, struct wdt_notify, entry);
		if (node != head)
			call_handle(node);
	}
	call_handle(head);
	spin_unlock_irqrestore(&lock, flags);
}

int register_hwdog_notify(wtd_notify_t func)
{
	return register_notify_common(func, &hwdog_notify_head);
}
EXPORT_SYMBOL(register_hwdog_notify);

int unregister_hwdog_notify(wtd_notify_t func)
{
	return unregister_notify_common(func, &hwdog_notify_head);
}
EXPORT_SYMBOL(unregister_hwdog_notify);

int register_hwdog_final_notify(wtd_notify_t func)
{
	return register_final_notify_common(func, &hwdog_notify_head);
}
EXPORT_SYMBOL(register_hwdog_final_notify);

int unregister_hwdog_final_notify(void)
{
	return unregister_final_notify_common(&hwdog_notify_head);
}
EXPORT_SYMBOL(unregister_hwdog_final_notify);

void call_hwdog_notify(void)
{
	call_notify_common(&hwdog_notify_head);
}

int register_softdog_notify(wtd_notify_t func)
{
	return register_notify_common(func, &softdog_notify_head);
}
EXPORT_SYMBOL(register_softdog_notify);

int unregister_softdog_notify(wtd_notify_t func)
{
	return unregister_notify_common(func, &softdog_notify_head);
}
EXPORT_SYMBOL(unregister_softdog_notify);

int register_softdog_final_notify(wtd_notify_t func)
{
	return register_final_notify_common(func, &softdog_notify_head);
}
EXPORT_SYMBOL(register_softdog_final_notify);

int unregister_softdog_final_notify(void)
{
	return unregister_final_notify_common(&softdog_notify_head);
}
EXPORT_SYMBOL(unregister_softdog_final_notify);

void call_softdog_notify(void)
{
	call_notify_common(&softdog_notify_head);
}

